package server

import (
	"bytes"
	"errors"
	"fmt"
	"go.uber.org/zap"
	"live-core/core"
	"live-core/logging"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
)

var (
	Version   = "RTSP/1.0"
	UserAgent = "Lavf58.29.100"
	Session   = "000a959d6816"
)

var (
	OPTIONS  = "OPTIONS"
	DESCRIBE = "DESCRIBE"
	SETUP    = "SETUP"
	PLAY     = "PLAY"
	TEARDOWN = "TEARDOWN"
)

func RTSPServer() {
	logging.ZapLog.Info("RTSP服务启动成功")
	l, err := net.Listen("tcp", core.StreamManager.ServerRTSPPort())
	if err != nil {
		logging.ZapLog.Error(err.Error(), zap.Error(err))
		os.Exit(1)
	}
	defer func() {
		err := l.Close()
		if err != nil {
			logging.ZapLog.Error(err.Error(), zap.Error(err))
		}
	}()
	for {
		conn, err := l.Accept()
		if err != nil {
			logging.ZapLog.Error(err.Error(), zap.Error(err))
			os.Exit(1)
		}
		go rtspServerClientHandle(conn)
	}
}

func rtspServerClientHandle(conn net.Conn) {
	buf := make([]byte, 4096)
	uuid, channel, in, cSEQ := "", "0", 0, 0
	var playStarted bool
	defer func() {
		err := conn.Close()
		if err != nil {
			logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
		}

	}()
	err := conn.SetDeadline(time.Now().Add(10 * time.Second))
	if err != nil {
		logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
		return
	}
	for {
		n, err := conn.Read(buf)
		if err != nil {
			logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
			return
		}
		cSEQ = parsecSEQ(buf[:n])
		stage, err := parseStage(buf[:n])
		if err != nil {
			logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
		}
		err = conn.SetDeadline(time.Now().Add(60 * time.Second))
		logging.ZapLog.Warn(string(buf[:n]), zap.String("stream", uuid), zap.String("channel", channel))
		if err != nil {
			logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
			return
		}
		switch stage {
		case OPTIONS:
			if playStarted {
				err = rtspServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "Public": "DESCRIBE, SETUP, TEARDOWN, PLAY"})
				if err != nil {
					return
				}
				continue
			}
			uuid, channel, err = parseStreamChannel(buf[:n])
			if err != nil {
				logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
				return
			}
			if !core.StreamManager.ChannelExist(uuid, channel) {
				logging.ZapLog.Error(core.ErrorStreamNotFound.Error(), zap.String("stream", uuid), zap.String("channel", channel))
				err = rtspServerClientResponse(uuid, channel, conn, 404, map[string]string{"CSeq": strconv.Itoa(cSEQ)})
				if err != nil {
					return
				}
				return
			}
			core.StreamManager.ChannelRun(uuid, channel)
			err = rtspServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "Public": "DESCRIBE, SETUP, TEARDOWN, PLAY"})
			if err != nil {
				return
			}
		case SETUP:
			if !strings.Contains(string(buf[:n]), "interleaved") {
				err = rtspServerClientResponse(uuid, channel, conn, 461, map[string]string{"CSeq": strconv.Itoa(cSEQ)})
				if err != nil {
					return
				}
				continue
			}
			err = rtspServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "User-Agent:": UserAgent, "Session": Session, "Transport": "RTP/AVP/TCP;unicast;interleaved=" + strconv.Itoa(in) + "-" + strconv.Itoa(in+1)})
			if err != nil {
				return
			}
			in = in + 2
		case DESCRIBE:
			sdp, err := core.StreamManager.ChannelSDP(uuid, channel)
			if err != nil {
				logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
				return
			}
			err = rtspServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "User-Agent:": UserAgent, "Session": Session, "Content-Type": "application/sdp\r\nContent-Length: " + strconv.Itoa(len(sdp)), "sdp": string(sdp)})
			if err != nil {
				return
			}
		case PLAY:
			err = rtspServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "User-Agent:": UserAgent, "Session": Session})
			if err != nil {
				return
			}
			playStarted = true
			go rtspServerClientPlay(uuid, channel, conn)
		case TEARDOWN:
			err = rtspServerClientResponse(uuid, channel, conn, 200, map[string]string{"CSeq": strconv.Itoa(cSEQ), "User-Agent:": UserAgent, "Session": Session})
			if err != nil {
				return
			}
			return
		default:
			logging.ZapLog.Warn("stage bad", zap.String("stream", uuid), zap.String("channel", channel), zap.Any("stage", stage))
		}
	}
}

func rtspServerClientPlay(uuid string, channel string, conn net.Conn) {
	cid, _, ch, err := core.StreamManager.ClientAdd(uuid, channel, core.RTSP)
	if err != nil {
		logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
		return
	}
	defer func() {
		core.StreamManager.ClientDelete(uuid, cid, channel)
		logging.ZapLog.Info("Client offline", zap.String("stream", uuid), zap.String("channel", channel))
		err := conn.Close()
		if err != nil {
			logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
		}
	}()

	noVideo := time.NewTimer(10 * time.Second)

	for {
		select {
		case <-noVideo.C:
			return
		case pck := <-ch:
			noVideo.Reset(10 * time.Second)
			_, err := conn.Write(*pck)
			if err != nil {
				logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
				return
			}
		}
	}
}

func rtspServerClientResponse(uuid string, channel string, conn net.Conn, status int, headers map[string]string) error {
	var sdp string
	builder := bytes.Buffer{}
	builder.WriteString(fmt.Sprintf(Version+" %d %s\r\n", status, GetStatusText(status)))
	for k, v := range headers {
		if k == "sdp" {
			sdp = v
			continue
		}
		builder.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
	}
	builder.WriteString(fmt.Sprintf("\r\n"))
	builder.WriteString(sdp)
	logging.ZapLog.Debug(builder.String(), zap.String("stream", uuid), zap.String("channel", channel))
	if _, err := conn.Write(builder.Bytes()); err != nil {
		logging.ZapLog.Error(err.Error(), zap.String("stream", uuid), zap.String("channel", channel), zap.Error(err))
		return err
	}
	return nil
}

func parsecSEQ(buf []byte) int {
	return core.StringToInt(core.StringInBetween(string(buf), "CSeq: ", "\r\n"))
}

func parseStage(buf []byte) (string, error) {
	st := strings.Split(string(buf), " ")
	if len(st) > 0 {
		return st[0], nil
	}
	return "", errors.New("解析阶段失败 " + string(buf))
}

func parseStreamChannel(buf []byte) (string, string, error) {
	uri := core.StringInBetween(string(buf), " ", " ")
	st := strings.Split(uri, "/")
	if len(st) >= 5 {
		return st[3], st[4], nil
	}
	return "", "0", errors.New("解析流出错 " + string(buf))
}
